﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Linq;
using System.Reflection;
using System.Threading;
using Sofire.DataComm.Net.Async;

namespace Sofire.DataComm.Remoting.Client
{
    /// <summary>
    /// 表示一个远程客户端。
    /// </summary>
    public class RemotingClient : IDisposable, IChannel
    {
        private AsyncSocketClient _innerClient;

        private IPEndPoint _serverIPEndPoint;
        private List<MethodSyncInfo> _syncInfos;
        private string _shortAddress;

        private long InvokingCount = 0;
        private RegisterInfo _Info;
        private bool _SyncInvokMethod = true;

        /// <summary>
        /// 调用同步对象。防止一些意外数据。
        /// </summary>
        private readonly object InvokingSyncObject = new object();

        event CommunicationStateEventHandler ICommunicationState.StateChanged
        {
            add { this._innerClient.StateChanged += value; }
            remove { this._innerClient.StateChanged -= value; }
        }

        CommunicationState ICommunicationState.State
        {
            get { return this._innerClient.State; }
        }

        RegisterInfo IChannel.Info
        {
            get
            {
                return _Info;
            }
        }

        string IChannel.SessionID
        {
            get
            {
                RemotingLoginResultValue resultValue;

                lock(RemotingClient.ServicesMethods)
                {
                    if(!RemotingClient.ServicesMethods.TryGetValue(this._Info.ChannelType, out resultValue)) return null;
                }

                lock(resultValue) return resultValue.SessionID;
            }
        }

        bool IChannel.AsynInvokMethod
        {
            get
            {
                return !_SyncInvokMethod;
            }
            set
            {
                _SyncInvokMethod = !value;
            }
        }

        object[] IChannel.DynamicInvoke(MethodInfo methodInfo, params object[] parameters)
        {
            var localIdent = this._Info.FindMethodIdent(methodInfo);
            if(localIdent == -1) throw new ArgumentException("非法的契约方法！", "methodInfo");
            return this.InvokeMethod(localIdent, parameters);
        }

        /// <summary>
        /// 指定连接地址和契约名称(<see cref="System.Type.FullName"/>)，初始化 <see cref="Sofire.DataComm.Remoting.Client.RemotingClient"/> 的新实例。
        /// </summary>
        /// <param name="registerInfo">注册信息。</param>
        public RemotingClient(RegisterInfo registerInfo)
        {
            this._Info = registerInfo;
            var address = this._Info.Address;
            // tcp://localhost:8080/HostName/ServiceName.v

            if(!address.StartsWith("tcp://", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException("连接地址起始必须是“tcp://”。");
            address = address.Remove(0, 6);
            if(!address.EndsWith(".v", StringComparison.OrdinalIgnoreCase)) throw new ArgumentException("连接地址结束必须是“.v”。");

            IPEndPoint endpoint;
            try
            {
                var index = address.IndexOf('/');
                var ipAndPort = address.Substring(0, index).Split(':');

                endpoint = new IPEndPoint(NetExts.ParseAddress(ipAndPort[0]), int.Parse(ipAndPort[1]));
                this._shortAddress = address.Substring(index + 1);
            }
            catch(Exception)
            {
                throw new ArgumentException("错误的连接地址，无法分析 IP 地址和端口。");
            }
            this._serverIPEndPoint = endpoint;
            this.OpenConnection();
        }

        private void SwitchFailedState(Exception e)
        {
            this._innerClient.OnSwitchState(CommunicationState.Failed, e);
        }

        void ICommunication.Open()
        {
            this.OpenConnection();
        }

        private void OpenConnection()
        {
            if(this._innerClient != null && this._innerClient.IsRunning) return;
            try
            {
                this._innerClient = new AsyncSocketClient(this._serverIPEndPoint.ToString(), new Net.SocketInfo(Net.ProtocolMode.Tcp, this._serverIPEndPoint));

                this._innerClient.Open();
                if(this._innerClient.IsRunning)
                {
                    this._innerClient.AsyncReceive += new AsyncSocketReceiveEventHandler(_innerClient_AsyncReceive);
                    lock(this._Info.ChannelType)
                    {
                        this.SyncContract();
                    }
                }
            }
            catch(Exception ex)
            {
                this.CloseConnection();
                this.SwitchFailedState(ex);
                throw ex;
            }
        }

        void _innerClient_AsyncReceive(object sender, AsyncSocketReceiveEventArgs e)
        {
            var result = e.GetEntity<RemotingResult>();
            var token = result.Token;
            ResultList.Add(result.Token, result);
            WaitingList[token].Set();

        }

        private void CloseConnection()
        {
            if(this._innerClient != null && this._innerClient.IsRunning)
            {
                while(Interlocked.CompareExchange(ref this.InvokingCount, 0, 0) != 0)
                {
                    System.Threading.Thread.Sleep(100);
                }
                this._innerClient.Close();
            }
        }

        void ICommunication.Close()
        {
            this.CloseConnection();
        }

        void IDisposable.Dispose()
        {
            this.CloseConnection();
        }

        private readonly Dictionary<long, AutoResetEvent> WaitingList = new Dictionary<long, AutoResetEvent>(33);
        private readonly Dictionary<long, RemotingResult> ResultList = new Dictionary<long, RemotingResult>(33);
        private static readonly Dictionary<Type, RemotingLoginResultValue> ServicesMethods = new Dictionary<Type, RemotingLoginResultValue>();

        private void SendServer(RemotingArguments args)
        {
            var bytes = RemotingShare.Formatter.FastWriteBytes(args);
            this._innerClient.Send(bytes);
        }
        
        private void SyncContract()
        {
            using(var ev = new AutoResetEvent(false))
            {
                var token = Interlocked.Increment(ref this.InvokingCount);
                WaitingList[token] = ev;

                try
                {
                    var channelType = this._Info.ChannelType;
                    RemotingLoginResultValue lastResultValue;
                    string sessionID = null;
                    int serviceVersion = -1;

                    lock(RemotingClient.ServicesMethods)
                    {
                        if(RemotingClient.ServicesMethods.TryGetValue(channelType, out lastResultValue))
                        {
                            this._syncInfos = lastResultValue.Methods;
                        }
                    }
                    if(lastResultValue != null)
                    {
                        lock(lastResultValue)
                        {
                            sessionID = lastResultValue.SessionID;
                            serviceVersion = lastResultValue.ServiceVersion;
                        }
                    }

                    RemotingArguments args = new RemotingArguments(
                        token
                        , new RemotingCertificate(this._shortAddress
                            , this._Info.ChannelType.FullName
                            , this._Info.Username, this._Info.Password
                            , sessionID)
                        , serviceVersion);
                    this.SendServer(args);
                    ev.WaitOne();
                    var result = ResultList[token];

                    var ex = result.Exception;
                    if(ex == null)
                    {
                        var newResultValue = result.Value as RemotingLoginResultValue;
                        if(newResultValue.Methods == null)
                        {
                            lock(lastResultValue)
                            {
                                if(lastResultValue.SessionID != newResultValue.SessionID)
                                {
                                    lastResultValue.SessionID = newResultValue.SessionID;
                                }
                            }
                        }
                        else
                        {
                            this._syncInfos = newResultValue.Methods;
                            lock(RemotingClient.ServicesMethods)
                            {
                                RemotingClient.ServicesMethods[channelType] = newResultValue;
                            }
                        }



                        //this._syncInfos = RemotingShare.Formatter.FastReadBytes<List<MethodSyncInfo>>(result.Value as byte[]);
                    }
                    else
                    {
                        throw ex;
                    }
                }
                finally
                {
                    WaitingList.Remove(token);
                    ResultList.Remove(token);
                    Interlocked.Decrement(ref this.InvokingCount);
                }
            }
        }

        /// <summary>
        /// 调用指定参数的远程方法。
        /// </summary>
        /// <param name="localIdent">本地方法的索引。</param>
        /// <param name="parameters">方法的参数集合。</param>
        /// <returns>一组方法调用后的返回值。索引 (0) 表示方法的返回值，其他表示 "ref" 或 "out" 的返回值。</returns>
        protected object[] InvokeMethod(int localIdent, params object[] parameters)
        {
            if(_SyncInvokMethod) Monitor.Enter(InvokingSyncObject);
            using(var ev = new AutoResetEvent(false))
            {
                var token = Interlocked.Increment(ref this.InvokingCount);
                WaitingList[token] = ev;

                bool closed = false;
                try
                {
                    if(this._innerClient.IsRunning)
                    {

                        var methodImpl = this._Info.GetMethod(localIdent);

                        var ident = methodImpl.GetMethodIndex(this._syncInfos);

                        if(ident < 0) throw new Exception(string.Format("无法找到与服务器匹配的 '{0}' 方法。", methodImpl.Name));

                        RemotingArguments args = new RemotingArguments(token, ident, parameters);

                        this.SendServer(args);
                        ev.WaitOne();
                        var result = ResultList[token];

                        var ex = result.Exception;
                        if(ex != null)
                        {
                            closed = result.IsTerminated;
                            throw ex;
                        }

                        return result.Value as object[];

                    }
                    throw new TerminateException("连接已关闭。");
                }
                finally
                {
                    WaitingList.Remove(token);
                    ResultList.Remove(token);
                    Interlocked.Decrement(ref this.InvokingCount);
                    if(_SyncInvokMethod) Monitor.Exit(InvokingSyncObject);
                    if(closed) this.CloseConnection();
                }
            }
        }

        /// <summary>
        /// 析构方法。
        /// </summary>
        ~RemotingClient()
        {
            this.CloseConnection();
        }

    }
}
